/*
 * init.c
 *
 * Simple code to load modules, mount root, and get things going. Uses
 * dietlibc to keep things small.
 *
 * Erik Troan (ewt@redhat.com)
 * Jeremy Katz (katzj@redhat.com)
 *
 * Copyright 2002-2004 Red Hat Software
 *
 * This software may be freely redistributed under the terms of the GNU
 * public license.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

/* init is a very simple script interpretor designed to be as small as
 * possible. It is primarily designed to run simple linuxrc scripts on an initrd
 * image. Arguments to commands may be enclosed in either single or double
 * quotes to allow spaces to be included in the arguments. Spaces outside of
 * quotations always delineate arguments, and so backslash escaping is supported.
 * 
 * There are two types of commands, built in and external. External commands
 * are run from the filesystem via execve(). If commands names are given without
 * a path, init will search it's builtin PATH, which is /usr/bin, /usr/sbin.
 * 
 * Currently, init supports the following built in commands.
 *
 * echo [item]* [> filename]
 * Echo the text strings given to a file, with a space in between each
 * item. The output may be optionally redirected to a file.
 *
 * sleep num
 * Sleep for num seconds.
 * 
 * mkdir [-p] path
 * Create the directory "path". If -p is specified, this command
 * will not complain if the directory exists. Note this is a subset of the
 * standard mkdir -p behavior.
 * 
 * insmod file
 * Insert a module into the kernel.
 *
 * mount -o opts -t type device mntpoint
 * Mount a filesystem. It does not support NFS, and it must be used in
 * the form given above (arguments must go first).  If "device" is of the
 * form dev-tag (LABEL=xxx or UUID=xxx or UUID_SUB=xxx), it will be
 * searched through libblkid. Normal mount(2) options are supported.
 * The defaults mount option is silently ignored.
 * 
 * mount-file mntpoint type opts file
 * Mount a file using a loop device.
 * 
 * mount-btrfs mntpoint opts device1 [device2...]
 * Mount a btrfs filesystem. User can specify multiple devices. Devices
 * can be specified in the dev-tag form.
 *
 * mount-bcachefs mntpoint opts device1 [device2...]
 * Mount a bcachefs filesystem. User can specify multiple devices. Devices
 * can be specified in the dev-tag form.
 *
 * umount path
 * Unmount the filesystem mounted at path.
 *
 * switchroot newrootpath [init command]
 * Make the filesystem mounted at newrootpath the new root
 * filesystem by moving the mountpoint.  This will only work in 2.6 or
 * later kernels.
 * 
 * lvm-lv-activate dev-tag vg-name lv-name
 * Activate the LVM2 logical volume specified by vg-name and lv-name. The logical
 * volume must have a tag (LABEL=xxx or UUID=xxx or UUID_SUB=xxx) that is
 * specifed by dev-tag.
 *
 * bcache-cache-device-activate cache-set-uuid device
 * Activate the cache device for bcache.
 *
 * bcache-backing-device-activate dev-tag device
 * Activate the backing device for bcache. The bcache device must have a tag
 * (LABEL=xxx or UUID=xxx or UUID_SUB=xxx) that is specified by dev-tag.
 *
 * bcache-activate-finish
 * Finish activating all the bcache backing device and cache device.
 */

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <libkmod.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/wait.h>
#include <linux/loop.h>
#include <blkid/blkid.h>


#define MAX(a, b) ((a) > (b) ? a : b)

#define STATFS_RAMFS_MAGIC    0x858458f6
#define STATFS_TMPFS_MAGIC    0x01021994

#define CMDLINESIZE 1024

#define STARTUPRC "startup.rc"

#define MAX_INIT_ARGS 32

#define PATH "/bin:/sbin:/usr/bin:/usr/sbin"

char * program_name = "init";
int kmsg_fd = -1;
/*@null@*/ blkid_cache mycache = NULL;
int bcache_count = 0;
int loop_dev_count = 0;
bool testing = false;
bool quiet = false;

char * env[] = {
    "PATH=" PATH,
    NULL
};

static void vShowMessage(int level, const char *format, va_list arg) {
    static int last_level = -1;
    char buf[1024] = "";
    int len = 0;

    if (last_level != -1 && level != last_level) {
        /* fatal, have to make kernel panic */
        exit(1);
    }

    if (testing) {
        if (last_level == -1) {
            len += snprintf(buf, sizeof(buf), "<%s> ", program_name);
        }
        len += vsnprintf(buf + len, sizeof(buf) - len, format, arg);
    } else {
        if (last_level == -1) {
            len += snprintf(buf, sizeof(buf), "<%d><%s> ", level, program_name);
        } else {
            len += snprintf(buf, sizeof(buf), "<%d>", level);
        }
        len += vsnprintf(buf + len, sizeof(buf) - len, format, arg);
    }

   if (testing) {
        printf("%s", buf);
   } else {
       write(kmsg_fd, buf, len);
   }

    if (buf[len - 1] != '\n') {
        last_level = level;
    } else {
        last_level = -1;
    }
}

static void showInfo(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vShowMessage(6, format, args);
    va_end(args);
}

static void showWarn(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vShowMessage(4, format, args);
    va_end(args);
}

static void showError(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vShowMessage(3, format, args);
    va_end(args);
}

const char * parseDevTag(const char * in, const char ** p_value) {
    const char * token;

    if (!strncmp("LABEL=", in, strlen("LABEL="))) {
        token = "LABEL";
    } else if (!strncmp("UUID=", in, strlen("UUID="))) {
        token = "UUID";
    } else if (!strncmp("UUID_SUB=", in, strlen("UUID_SUB="))) {
        token = "UUID_SUB";
    } else {
        token = NULL;
    }

    if (p_value != NULL) {
        if (token != NULL) {
            *p_value = in + strlen(token) + 1;
        } else {
            *p_value = NULL;
        }
    }

    return token;
}

char * getArg(char * cmd_name, char * cmd, char * end, char ** arg) {
    char quote = '\0';

    if (!cmd || cmd >= end) return NULL;

    while (isspace(*cmd) && cmd < end) cmd++;
    if (cmd >= end) return NULL;

    if (*cmd == '"')
    cmd++, quote = '"';
    else if (*cmd == '\'')
    cmd++, quote = '\'';

    if (quote != '\0') {
        *arg = cmd;

        /* This doesn't support \ escapes */
        while (cmd < end && *cmd != quote) cmd++;

        if (cmd == end) {
            showError("%s: quote mismatch for %s\n", cmd_name, *arg);
            return NULL;
        }

        *cmd = '\0';
        cmd++;
    } else {
        *arg = cmd;
        while (!isspace(*cmd) && cmd < end) cmd++;
        *cmd = '\0';
        if (**arg == '$')
            *arg = getenv(*arg+1);
        if (*arg == NULL)
            *arg = "";
    }

    cmd++;

    while (isspace(*cmd)) cmd++;

    return cmd;
}

/* get the contents of the kernel command line from /proc/cmdline */
static char * getKernelCmdLine(char * cmd_name) {
    int fd, i;
    char * buf;

    fd = open("/proc/cmdline", O_RDONLY, 0);
    if (fd < 0) {
        showError("%s: failed to open /proc/cmdline: %d\n", cmd_name, errno);
        return NULL;
    }

    buf = malloc(CMDLINESIZE);
    if (!buf)
        return buf;

    i = read(fd, buf, CMDLINESIZE);
    if (i < 0) {
        showError("%s: failed to read /proc/cmdline: %d\n", cmd_name, errno);
        (void)close(fd);
        return NULL;
    }

    (void)close(fd);
    if (i == 0)
        buf[0] = '\0';
    else
        buf[i - 1] = '\0';
    return buf;
}

static int hasKernelArg(char * cmd_name, char * arg) {
    char * start, * cmdline;

    cmdline = start = getKernelCmdLine(cmd_name);
    if (start == NULL) {
        return 0;
    }
    while (*start != '\0') {
        if (isspace(*start)) {
            start++;
            continue;
        }
        if (strncmp(start, arg, strlen(arg)) == 0) {
            char * next = start + strlen(arg);
            if (*next == '\0' || isspace(*next)) {
                return 1;
            }
        }
        while (*++start != '\0' && !isspace(*start))
            ;
    }

    return 0;
}

/* get the start of a kernel arg "arg".  returns everything after it
 * (useful for things like getting the args to init=).  so if you only
 * want one arg, you need to terminate it at the n */
static char * getKernelArg(char * cmd_name, char * arg) {
    char * start, * cmdline;

    cmdline = start = getKernelCmdLine(cmd_name);
    if (start == NULL) {
        return NULL;
    }
    while (*start != '\0') {
        if (isspace(*start)) {
            start++;
            continue;
        }
        if (strncmp(start, arg, strlen(arg)) == 0) {
            return start + strlen(arg);
        }
        while (*++start != '\0' && !isspace(*start))
            ;
    }

    return NULL;
}

static char * waitForDev(const char * cmd_name, const char * device) {
    const char * token;
    const char * value;

    token = parseDevTag(device, &value);
    if (token != NULL) {
        while (true) {
            char * devName = blkid_evaluate_tag(token, value, &mycache);
            if (devName != NULL) {
                return devName;
            }
            showError("%s: failed to get device specified by %s, retrying\n", cmd_name, device);
            (void)sleep(1);
            (void)blkid_probe_all(mycache);
            (void)blkid_probe_all_removable(mycache);
        }
    } else {
        while (true) {
            if (access(device, F_OK) == 0) {
                return NULL;
            }
            showError("%s: failed to get device specified by %s, retrying\n", cmd_name, device);
            (void)sleep(1);
        }
    }
}

static char * setupLoopDev(const char * cmd_name, const char * file, bool read_only) {
    static char device[PATH_MAX];
    int dev;
    int fd;
    struct loop_info loopInfo;

    snprintf(device, sizeof(device), "/dev/loop%d", loop_dev_count);
    while (true) {
        dev = open(device, O_RDWR, 0);
        if (dev >= 0) {
            break;
        }
        showError("%s: failed to open %s: %d, retrying\n", cmd_name, device, errno);
        (void)sleep(1);
    }

    fd = open(file, read_only ? O_RDONLY : O_RDWR, 0);
    if (fd < 0) {
        showError("%s: failed to open %s: %d\n", cmd_name, file, errno);
        close(dev);
        return NULL;
    }

    if (ioctl(dev, LOOP_SET_FD, (long) fd)) {
        showError("%s: LOOP_SET_FD failed for %s: %d\n", cmd_name, device, errno);
        close(fd);
        close(dev);
        return NULL;
    }

    close(fd);

    /* loop device is occupied, whatever the operation result is */
    loop_dev_count++;

    memset(&loopInfo, 0, sizeof(loopInfo));
    strcpy(loopInfo.lo_name, file);
    if (ioctl(dev, LOOP_SET_STATUS, &loopInfo)) {
        showError("%s: LOOP_SET_STATUS failed for %s: %d\n", cmd_name, device, errno);
        close(dev);
        return NULL;
    }

    close(dev);

    return device;
}

/* remove all files/directories below dirName -- don't cross mountpoints */
static int recursiveRemove(char * cmd_name, int fd)
{
    struct stat rb;
    DIR * dir;
    int rc = -1;
    int dfd;

    if (!(dir = fdopendir(fd))) {
        showError("%s: failed to open directory\n", cmd_name);
        goto done;
    }

    /* fdopendir() precludes us from continuing to use the input fd */
    dfd = dirfd(dir);

    if (fstat(dfd, &rb)) {
        showError("%s: stat failed\n", cmd_name);
        goto done;
    }

    while (true) {
        struct dirent *d;
        int isdir = 0;

        errno = 0;
        if (!(d = readdir(dir))) {
            if (errno) {
                showError("%s: failed to read directory\n", cmd_name);
                goto done;
            }
            break;    /* end of directory */
        }

        if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
            continue;
#ifdef _DIRENT_HAVE_D_TYPE
        if (d->d_type == DT_DIR || d->d_type == DT_UNKNOWN)
#endif
        {
            struct stat sb;

            if (fstatat(dfd, d->d_name, &sb, AT_SYMLINK_NOFOLLOW)) {
                showError("%s: stat failed %s\n", cmd_name, d->d_name);
                continue;
            }

            /* remove subdirectories if device is same as dir */
            if (S_ISDIR(sb.st_mode) && sb.st_dev == rb.st_dev) {
                int cfd;

                cfd = openat(dfd, d->d_name, O_RDONLY);
                if (cfd >= 0) {
                    recursiveRemove(cmd_name, cfd);
                    close(cfd);
                }
                isdir = 1;
            } else
                continue;
        }

        if (unlinkat(dfd, d->d_name, isdir ? AT_REMOVEDIR : 0))
            showError("%s: failed to unlink %s\n", cmd_name, d->d_name);
    }

    rc = 0;    /* success */

done:
    if (dir)
        closedir(dir);
    return rc;
}

/* va_list causes segment fault, so I use this method */
#define MAX_ARGV_COUNT 127
static int runBinaryImpl(char *cmd_name, const char *bin, const char *argArray[], int argArrayLen) {
    char ** theArgv;
    char ** nextArg;
    int pid, wpid;
    int status;
    int i;

    if (bin[0] != '/') {
        showError("%s: binary executable \"%s\" is not specified in absolute path\n", cmd_name, bin);
        return 1;
    }
    if (access(bin, X_OK) != 0) {
        showError("%s: invalid binary executable \"%s\"\n", cmd_name, bin);
        return 1;
    }
    if (argArrayLen > MAX_ARGV_COUNT) {
        showError("%s: too many arguments\n", cmd_name);
        return 1;
    }

    theArgv = (char **)malloc(sizeof(char *) * (MAX_ARGV_COUNT + 1));
    if (theArgv == NULL) {
        showError("%s: out of memory\n", cmd_name);
        return 1;
    }

    nextArg = theArgv;
    *nextArg = strdup(bin);
    if (*nextArg == NULL) {
        showError("%s: out of memory\n", cmd_name);
        return 1;
    }
    nextArg++;

    for (i = 0; i < argArrayLen; i++) {
        *nextArg = strdup(argArray[i]);
        if (*nextArg == NULL) {
            showError("%s: out of memory\n", cmd_name);
            return 1;
        }
        nextArg++;
    }
    *nextArg = NULL;

    if (testing) {
        showInfo("run binary, %s", bin);
        for (nextArg = theArgv; *nextArg != NULL; nextArg++) {
            showInfo(" %s", *nextArg);
        }
        showInfo("\n");
    } else {
        if (!(pid = fork())) {
            /* child */
            execve(theArgv[0], theArgv, env);
            showError("%s: failed in exec of %s\n", cmd_name, theArgv[0]);
            return 1;
        }

        for (;;) {
            wpid = wait4(-1, &status, 0, NULL);
            if (wpid == -1) {
                showError("%s: failed to wait for process %d\n", cmd_name, wpid);
                return 1;
            }

            if (wpid != pid) {
                continue;
            }

            if (!WIFEXITED(status) || WEXITSTATUS(status)) {
                showError("%s: %s exited abnormally! (pid %d)\n", cmd_name, theArgv[0], pid);
                return 1;
            }

            break;
        }
    }

    return 0;
}

static int runBinary1(char *cmd_name, const char * bin, const char * arg1) {
    const char *argArray[1];
    argArray[0] = arg1;
    return runBinaryImpl(cmd_name, bin, argArray, 1);
}

static int runBinary2(char *cmd_name, const char *bin, const char *arg1, const char *arg2) {
    const char *argArray[2];
    argArray[0] = arg1;
    argArray[1] = arg2;
    return runBinaryImpl(cmd_name, bin, argArray, 2);
}

static int runBinary3(char *cmd_name, const char *bin, const char *arg1, const char *arg2, const char *arg3) {
    const char *argArray[3];
    argArray[0] = arg1;
    argArray[1] = arg2;
    argArray[2] = arg3;
    return runBinaryImpl(cmd_name, bin, argArray, 3);
}

static int runBinary4(char *cmd_name, const char *bin, const char *arg1, const char *arg2, const char *arg3, const char *arg4) {
    const char *argArray[4];
    argArray[0] = arg1;
    argArray[1] = arg2;
    argArray[2] = arg3;
    argArray[3] = arg4;
    return runBinaryImpl(cmd_name, bin, argArray, 4);
}

int insmodCommand(char * cmd, char * end) {
    char * filename;

    if (!(cmd = getArg("insmod", cmd, end, &filename))) {
        showError("insmod: missing file\n");
        return 1;
    }

    if (cmd < end) {
        showError("insmod: unexpected arguments\n");
        return 1;
    }

    if (!testing) {
        const char * null_config = NULL;
        struct kmod_ctx * ctx;
        struct kmod_module * mod;
        int err;

        ctx = kmod_new(NULL, &null_config);
        if (!ctx) {
            showError("insmod: kmod_new() failed\n");
            return 1;
        }

        err = kmod_module_new_from_path(ctx, filename, &mod);
        if (err < 0) {
            showError("insmod: could not load module %s: %s\n", filename, strerror(-err));
            kmod_unref(ctx);
            return 1;
        }

        err = kmod_module_insert_module(mod, 0, "");
        if (err < 0) {
            char *err_str;
            switch (-err) {
                case ENOEXEC:
                    err_str = "invalid module format";
                    break;
                case ENOENT:
                    err_str = "unknown symbol in module";
                    break;
                case ESRCH:
                    err_str = "module has wrong symbol version";
                    break;
                case EINVAL:
                    err_str = "invalid parameters";
                    break;
                default:
                    err_str = strerror(err);
                    break;
            }

            showError("insmod: could not insert module %s: %s\n", filename, err_str);
            kmod_module_unref(mod);
            kmod_unref(ctx);
            return 1;
        }

        kmod_module_unref(mod);
        kmod_unref(ctx);
    }
    else {
        showInfo("(insmod) %s\n", filename);
    }

    return 0;
}

int _implMountConvertOptions(char * cmd_name, char * options, int * pflags, char * buf, int buf_len) {
    char * start = options;
    char * end;

    while (*start) {
        end = strchr(start, ',');
        if (!end) {
            end = start + strlen(start);
        } else {
            *end = '\0';
            end++;
        }

        if (!strcmp(start, "ro"))
            *pflags |= MS_RDONLY;
        else if (!strcmp(start, "rw"))
            *pflags &= ~MS_RDONLY;
        else if (!strcmp(start, "nosuid"))
            *pflags |= MS_NOSUID;
        else if (!strcmp(start, "suid"))
            *pflags &= ~MS_NOSUID;
        else if (!strcmp(start, "nodev"))
            *pflags |= MS_NODEV;
        else if (!strcmp(start, "dev"))
            *pflags &= ~MS_NODEV;
        else if (!strcmp(start, "noexec"))
            *pflags |= MS_NOEXEC;
        else if (!strcmp(start, "exec"))
            *pflags &= ~MS_NOEXEC;
        else if (!strcmp(start, "sync"))
            *pflags |= MS_SYNCHRONOUS;
        else if (!strcmp(start, "async"))
            *pflags &= ~MS_SYNCHRONOUS;
        else if (!strcmp(start, "nodiratime"))
            *pflags |= MS_NODIRATIME;
        else if (!strcmp(start, "diratime"))
            *pflags &= ~MS_NODIRATIME;
        else if (!strcmp(start, "noatime"))
            *pflags |= MS_NOATIME;
        else if (!strcmp(start, "atime"))
            *pflags &= ~MS_NOATIME;
        else if (!strcmp(start, "strictatime"))
            *pflags |= MS_STRICTATIME;
        else if (!strcmp(start, "relatime"))
            *pflags |= MS_RELATIME;
        else if (!strcmp(start, "remount"))
            *pflags |= MS_REMOUNT;
        else if (!strcmp(start, "defaults"))
            ;
        else {
            if (*buf) {
                if (strlen(buf) + 1 + strlen(start) + 1 > buf_len) {
                    showError("%s: converted options are too long\n", cmd_name);
                    return 1;
                }
                strcat(buf, ",");
                strcat(buf, start);
            } else {
                if (strlen(buf) + strlen(start) + 1 > buf_len) {
                    showError("%s: converted options are too long\n", cmd_name);
                    return 1;
                }
                strcat(buf, start);
            }
        }

        start = end;
    }

    return 0;
}

int _implMountConvertDevice(char * cmd_name, char * device, char * buf, int buf_len) {
    char * devName;

    devName = waitForDev(cmd_name, device);
    if (devName != NULL) {
        if (strlen(devName) + 1 > buf_len) {
            showError("%s: converted device name %s for %s is too long\n", cmd_name, devName, device);
            free(devName);
            return 1;
        }
        strcpy(buf, devName);
        free(devName);
    } else {
        if (strlen(device) + 1 > buf_len) {
            showError("%s: out buffer for device %s is too small\n", cmd_name, device);
            return 1;
        }
        strcpy(buf, device);
    }

    return 0;
}

int _implDoMount(char * cmd_name, char * fsType, char * options, int flags, char * device, char * mntPoint) {
    if (testing) {
        showInfo("(mount) mount -o '%s' -t '%s' '%s' '%s'%s%s%s%s%s%s%s%s%s\n",
            options, fsType, device, mntPoint,
            (flags & MS_RDONLY) ? " +ro" : "",
            (flags & MS_NOSUID) ? " +nosuid " : "",
            (flags & MS_NODEV) ? " +nodev " : "",
            (flags & MS_NOEXEC) ? " +noexec " : "",
            (flags & MS_SYNCHRONOUS) ? " +sync " : "",
            (flags & MS_REMOUNT) ? " +remount " : "",
            (flags & MS_NOATIME) ? " +noatime " : "",
            (flags & MS_STRICTATIME) ? " +strictatime": "",
            (flags & MS_RELATIME) ? " +relatime " : ""
        );
    } else {
        if (mount(device, mntPoint, fsType, flags, options)) {
            showError("%s: error %d mounting %s (%s)\n", cmd_name, errno, device, fsType);
            return 1;
        }
    }

    return 0;
}

int mountCommand(char * cmd, char * end) {
    char * fsType = NULL;
    char * device;
    char * mntPoint;
    char * options = NULL;
    int flags = MS_MGC_VAL;

    cmd = getArg("mount", cmd, end, &device);
    if (!cmd) {
        showError("usage: mount [-o <opts>] -t <type> <device> <mntpoint>\n");
        return 1;
    }

    while (cmd && *device == '-') {
        if (!strcmp(device, "--bind")) {
            flags = MS_BIND;
            fsType = "none";
        } else if (!strcmp(device, "-o")) {
            cmd = getArg("mount", cmd, end, &options);
            if (!cmd) {
                showError("mount: -o requires arguments\n");
                return 1;
            }
        } else if (!strcmp(device, "-t")) {
            if (!(cmd = getArg("mount", cmd, end, &fsType))) {
                showError("mount: missing filesystem type\n");
                return 1;
            }
        }

        cmd = getArg("mount", cmd, end, &device);
    }

    if (!cmd) {
        showError("mount: missing device\n");
        return 1;
    }

    if (!(cmd = getArg("mount", cmd, end, &mntPoint))) {
        showError("mount: missing mount point\n");
        return 1;
    }

    if (!fsType) {
        showError("mount: filesystem type expected\n");
        return 1;
    }

    if (cmd < end) {
        showError("mount: unexpected arguments\n");
        return 1;
    }

    /* need to deal with options */ 
    if (options) {
        char * newOpts = alloca(strlen(options) + 1);
        *newOpts = '\0';
        if (_implMountConvertOptions("mount", options, &flags, newOpts, strlen(options) + 1)) {
            /* callee prints error message */
            return 1;
        }
        options = newOpts;
    }

    if (*device != '/') {
        char * newDevice = alloca(strlen(device) + 1);
        *newDevice = '\0';
        if (_implMountConvertDevice("mount", device, newDevice, strlen(device) + 1)) {
            /* callee prints error message */
            return 1;
        }
        device = newDevice;
    }

    if (_implDoMount("mount", fsType, options, flags, device, mntPoint)) {
        /* callee prints error message */
        return 1;
    }

    return 0;
}

int mountBtrfsCommand(char * cmd, char * end) {
    const char * usage = "usage: mount-btrfs <mntpoint> <opts> <device1> [device2...]";
    char * mntPoint;
    char * options;
    int flags = MS_MGC_VAL;
    char realOptions[4096] = "";
    char * lastDev = NULL;
    int i;

    /* parse <mntpoint> */
    cmd = getArg("mount-btrfs", cmd, end, &mntPoint);
    if (!cmd) {
        showError("%s\n", usage);
        return 1;
    }

    /* parse <opts> */
    cmd = getArg("mount-btrfs", cmd, end, &options);
    if (!cmd) {
        showError("%s\n", usage);
        return 1;
    }
    if (_implMountConvertOptions("mount-btrfs", options, &flags, realOptions, 4096)) {
        /* callee prints error message */
        return 1;
    }

    /* parse <device1> <device2> ... */
    for (i = 0; cmd < end; i++) {
        char * prefix = (*realOptions ? ",device=" : "device=");
        char * device;

        cmd = getArg("mount-btrfs", cmd, end, &device);
        if (!cmd) {
            showError("mount-btrfs: failed to parse device %d\n", i + 1);
            return 1;
        }

        if (strlen(realOptions) + strlen(prefix) + 1 > 4096) {
            showError("mount-btrfs: options are too long\n");
            return 1;
        }
        strcat(realOptions, prefix);

        lastDev = realOptions + strlen(realOptions);
        if (_implMountConvertDevice("mount-btrfs", device, lastDev, 4096 - (lastDev - realOptions))) {
            /* callee prints error message */
            return 1;
        }
    }
    if (i == 0) {
        showError("%s\n", usage);
        return 1;
    }

    if (_implDoMount("mount-btrfs", "btrfs", realOptions, flags, lastDev, mntPoint)) {
        /* callee prints error message */
        return 1;
    }

    return 0;
}

int mountBcachefsCommand(char * cmd, char * end) {
    const char * usage = "usage: mount-bcachefs <mntpoint> <opts> <device1> [device2...]";
    char * mntPoint;
    char * options;
    int flags = MS_MGC_VAL;
    char realOptions[4096] = "";
    char realDevices[4096] = "";
    int i;

    /* parse <mntpoint> */
    cmd = getArg("mount-bcachefs", cmd, end, &mntPoint);
    if (!cmd) {
        showError("%s\n", usage);
        return 1;
    }

    /* parse <opts> */
    cmd = getArg("mount-bcachefs", cmd, end, &options);
    if (!cmd) {
        showError("%s\n", usage);
        return 1;
    }
    if (_implMountConvertOptions("mount-bcachefs", options, &flags, realOptions, 4096)) {
        /* callee prints error message */
        return 1;
    }

    /* parse <device1> <device2> ... */
    for (i = 0; cmd < end; i++) {
        char * prefix = (*realDevices ? "," : "");
        char * device;

        cmd = getArg("mount-bcachefs", cmd, end, &device);
        if (!cmd) {
            showError("mount-bcachefs: failed to parse device %d\n", i + 1);
            return 1;
        }

        if (strlen(realDevices) + strlen(prefix) + 1 > 4096) {
            showError("mount-bcachefs: options are too long\n");
            return 1;
        }
        strcat(realDevices, prefix);

        if (_implMountConvertDevice("mount-bcachefs", device, realDevices + strlen(realDevices), 4096 - strlen(realDevices))) {
            /* callee prints error message */
            return 1;
        }
    }
    if (i == 0) {
        showError("%s\n", usage);
        return 1;
    }

    if (_implDoMount("mount-bcachefs", "bcachefs", realOptions, flags, realDevices, mntPoint)) {
        /* callee prints error message */
        return 1;
    }

    return 0;
}

int mountFileCommand(char * cmd, char * end) {
    const char * usage = "usage: mount-file <mntpoint> <type> <opts> <file>";
    char * mntPoint;
    char * fsType;
    char * options;
    int flags = MS_MGC_VAL;
    char realOptions[4096] = "";
    char * filePath;
    char * loopDev;

    /* parse <mntpoint> */
    cmd = getArg("mount-file", cmd, end, &mntPoint);
    if (!cmd) {
        showError("%s\n", usage);
        return 1;
    }

    /* parse <type> */
    cmd = getArg("mount-file", cmd, end, &fsType);
    if (!cmd) {
        showError("%s\n", usage);
        return 1;
    }

    /* parse <opts> */
    cmd = getArg("mount-file", cmd, end, &options);
    if (!cmd) {
        showError("%s\n", usage);
        return 1;
    }
    if (_implMountConvertOptions("mount-file", options, &flags, realOptions, 4096)) {
        /* callee prints error message */
        return 1;
    }

    /* parse <file> */
    cmd = getArg("mount-file", cmd, end, &filePath);
    if (!cmd) {
        showError("%s\n", usage);
        return 1;
    }

    loopDev = setupLoopDev("mount-file", filePath, true);
    if (!loopDev) {
        /* callee prints error message */
        return 1;
    }

    if (_implDoMount("mount-file", fsType, realOptions, flags, loopDev, mntPoint)) {
        /* callee prints error message */
        /* don't release the loop device */
        return 1;
    }

    return 0;
}

int mountOverlayfsCommand(char * cmd, char * end) {
    const char * usage = "usage: mount-overlayfs <mntpoint> <opts> <lowerdir> <upperdir> <workdir>";
    const char * prefixList[] = { "lowerdir=", "upperdir=", "workdir=" };
    char * mntPoint;
    char * options;
    int flags = MS_MGC_VAL;
    char realOptions[4096] = "";
    int i;

    /* parse <mntpoint> */
    cmd = getArg("mount-overlayfs", cmd, end, &mntPoint);
    if (!cmd) {
        showError("%s\n", usage);
        return 1;
    }

    /* parse <opts> */
    cmd = getArg("mount-overlayfs", cmd, end, &options);
    if (!cmd) {
        showError("%s\n", usage);
        return 1;
    }
    if (_implMountConvertOptions("mount-overlayfs", options, &flags, realOptions, 4096)) {
        /* callee prints error message */
        return 1;
    }

    /* parse <lowerdir>, <uppderdir>, <workdir> */
    for (i = 0; i < 3; i++) {
        const char * comma = *realOptions ? "," : "";
        char * thedir;

        cmd = getArg("mount-overlayfs", cmd, end, &thedir);
        if (!cmd) {
            showError("%s\n", usage);
            return 1;
        }

        if (strlen(realOptions) + strlen(comma) + strlen(prefixList[i]) + strlen(thedir) + 1 > 4096) {
            showError("mount-overlayfs: options are too long\n");
            return 1;
        }

        strcat(realOptions, comma);
        strcat(realOptions, prefixList[i]);
        strcat(realOptions, thedir);
    }

    if (_implDoMount("mount-overlayfs", "overlay", realOptions, flags, "none", mntPoint)) {
        /* callee prints error message */
        return 1;
    }

    return 0;
}

int otherCommand(char * bin, char * cmd, char * end, int doFork) {
    char ** args;
    char ** nextArg;
    int pid, wpid;
    int status;
    char fullPath[255];
    static const char * sysPath = PATH;
    const char * pathStart;
    const char * pathEnd;
    char * stdoutFile = NULL;
    int stdoutFd = 0;

    args = (char **)malloc(sizeof(char *) * 128);
    if (!args)
        return 1;
    nextArg = args;

    if (!strchr(bin, '/')) {
        pathStart = sysPath;
        while (*pathStart) {
            pathEnd = strchr(pathStart, ':');

            if (!pathEnd) pathEnd = pathStart + strlen(pathStart);

            strncpy(fullPath, pathStart, pathEnd - pathStart);
            fullPath[pathEnd - pathStart] = '/';
            strcpy(fullPath + (pathEnd - pathStart + 1), bin); 

            pathStart = pathEnd;
            if (*pathStart) pathStart++;

            if (access(fullPath, X_OK) == 0) {
            bin = fullPath;
            break;
            }
        }
    }

    *nextArg = strdup(bin);

    while (cmd && cmd < end) {
        nextArg++;
        cmd = getArg("ERROR", cmd, end, nextArg);
    }
    
    if (cmd) nextArg++;
    *nextArg = NULL;

    /* if the next-to-last arg is a >, redirect the output properly */
    if (((nextArg - args) >= 2) && !strcmp(*(nextArg - 2), ">")) {
        stdoutFile = *(nextArg - 1);
        *(nextArg - 2) = NULL;

        stdoutFd = open(stdoutFile, O_CREAT | O_RDWR | O_TRUNC, 0600);
        if (stdoutFd < 0) {
            showError("ERROR: failed to open %s: %d\n", stdoutFile, errno);
            return 1;
        }
    }

    if (testing) {
        showInfo("%s ", bin);
        nextArg = args + 1;
        while (*nextArg)
            showInfo(" '%s'", *nextArg++);
        if (stdoutFile)
            showInfo(" (> %s)", stdoutFile);
        showInfo("\n");
    } else {
        if (!doFork || !(pid = fork())) {
            /* child */
            dup2(stdoutFd, 1);
            execve(args[0], args, env);
            showError("ERROR: failed in exec of %s\n", args[0]);
            return 1;
        }

        close(stdoutFd);

        for (;;) {
            wpid = wait4(-1, &status, 0, NULL);
            if (wpid == -1) {
                showError("ERROR: failed to wait for process %d\n", wpid);
                return 1;
            }

            if (wpid != pid)
                continue;

            if (!WIFEXITED(status) || WEXITSTATUS(status)) {
                showError("ERROR: %s exited abnormally! (pid %d)\n", args[0], pid);
                return 1;
            }
            break;
        }
    }

    return 0;
}

/* This is based on code from util-linux/sys-utils/run_init.c */
int switchrootCommand(char * cmd, char * end) {
    char * newroot;
    const char * initprogs[] = { "/sbin/init", "/etc/init", "/bin/init", "/bin/sh", NULL };
    const char * umounts[] = { "/dev", "/tmp", "/run", "/proc", "/sys", NULL };
    struct stat newroot_stat;
    char * init = NULL, * cmdline = NULL;
    char ** initargs;
    int fd, cfd, i;
    struct statfs stfs;

    if (!(cmd = getArg("switchroot", cmd, end, &newroot))) {
        showError("switchroot: new root mount point expected\n");
        return 1;
    }

    if (cmd < end) {
        if (!(cmd = getArg("switchroot", cmd, end, &init))) {
            showError("switchroot: init program expected\n");
            return 1;
        }

        if (cmd < end) {
            cmdline = strndup(cmd, end - cmd);
        }
    }

    if (init == NULL) {
        init = getKernelArg("switchroot", "init=");
        if (init == NULL) {
            cmdline = getKernelCmdLine("switchroot");
        }
    }

    if (!testing) {
        if ((fd = open("/dev/console", O_RDWR)) < 0) {
            showError("switchroot: error opening /dev/console!!!!: %d\n", errno);
            return 1;
        }

        if (dup2(fd, 0) != 0) {
            showError("switchroot: error dup2'ing fd of %d to 0\n", fd);
        }
        if (dup2(fd, 1) != 1) {
            showError("switchroot: error dup2'ing fd of %d to 1\n", fd);
        }
        if (dup2(fd, 2) != 2) {
            showError("switchroot: error dup2'ing fd of %d to 2\n", fd);
        }
        if (fd > 2) {
            close(fd);
        }

        cfd = open("/", O_RDONLY);
        if (cfd < 0) {
            showError("switchroot: cannot open /\n");
            return 1;
        }

        if (fstatfs(cfd, &stfs) != 0) {
            showError("switchroot: stat failed /\n");
            return 1;
        }

        if (stfs.f_type != (__SWORD_TYPE)STATFS_RAMFS_MAGIC && stfs.f_type != (__SWORD_TYPE)STATFS_TMPFS_MAGIC) {
            showError("switchroot: old root filesystem is not an initramfs\n");
            return 1;
        }

        if (stat(newroot, &newroot_stat) != 0) {
            showError("switchroot: stat failed %s\n", newroot);
            return 1;
        }

        for (i = 0; umounts[i] != NULL; i++) {
            char newmount[PATH_MAX];
            struct stat sb;

            snprintf(newmount, sizeof(newmount), "%s%s", newroot, umounts[i]);

            if (stat(newmount, &sb) != 0) {
                showError("switchroot: stat failed %s\n", newmount);
                return 1;
            }

            if (sb.st_dev != newroot_stat.st_dev) {
                /* mount point seems to be mounted already */
                umount2(umounts[i], MNT_DETACH);
                continue;
            }

            if (mount(umounts[i], newmount, NULL, MS_MOVE, NULL) < 0) {
                showError("switchroot: failed to mount moving %s to %s\n", umounts[i], newmount);
                return 1;
            }
        }

        if (chdir(newroot)) {
            showError("switchroot: chdir(%s) failed: %d\n", newroot, errno);
            return 1;
        }

        if (mount(".", "/", NULL, MS_MOVE, NULL) < 0) {
            showError("switchroot: mount failed: %d\n", errno);
            return 1;
        }

        if (chroot(".") || chdir("/")) {
            showError("switchroot: chroot() failed: %d\n", errno);
            return 1;
        }

        recursiveRemove("switchroot", cfd);
        close(cfd);

        if (init == NULL) {
            for (i = 0; initprogs[i] != NULL; i++) {
                if (access(initprogs[i], X_OK) == 0) {
                    init = strdup(initprogs[i]);
                    break;
                }
            }
        }

        i = 0;
        initargs = (char **)malloc(sizeof(char *)*(MAX_INIT_ARGS+1));
        if (cmdline && init) {
            initargs[i++] = strdup(init);
        } else {
            cmdline = init;
            initargs[0] = NULL;
        }

        if (cmdline != NULL) {
            char * chptr, * start;

            start = chptr = cmdline;
            for (; (i < MAX_INIT_ARGS) && (*start != '\0'); i++) {
                while (*chptr && !isspace(*chptr)) chptr++;
                if (*chptr != '\0') *(chptr++) = '\0';
                initargs[i] = strdup(start);
                start = chptr;
            }
        }

        initargs[i] = NULL;

        if (access(initargs[0], X_OK) != 0) {
            showWarn("switchroot: can't access %s\n", initargs[0]);
        }

        execv(initargs[0], initargs);
        showError("switchroot: exec of init (%s) failed!!!: %d\n", initargs[0], errno);
        return 1;
    }
    else {
        showInfo("(switchroot)\n");
        return 0;
    }
}

int echoCommand(char * cmd, char * end) {
    char * args[256];
    char ** nextArg = args;
    bool newline = true;
    int num = 0;
    int length = 0;
    char * outFilename = NULL;
    char * string;
    int i;

    /* parse arguments */
    while ((cmd = getArg("echo", cmd, end, nextArg))) {
        if (!strncmp("-n", *nextArg, MAX(2, strlen(*nextArg)))) {
            newline = false;
        } else {
            length += strlen(*nextArg);
            nextArg++, num++;
        }
    }
    length += num + 1;

    /* get redirect information */
    if ((nextArg - args >= 2) && !strcmp(*(nextArg - 2), ">")) {
        outFilename = *(nextArg - 1);
        num -= 2;
    }

    if (!outFilename && !newline) {
        showError("echo: -n can only be specified with redirection\n");
        return 1;
    }

    if (!outFilename && quiet) {
        return 0;
    }

    /* build string by arguments */
    string = (char *)malloc(length * sizeof(char));
    if (!string) {
        showError("echo: out of memory\n");
        return 1;
    }
    *string = '\0';

    for (i = 0; i < num;i ++) {
        if (i) strcat(string, " ");
        strncat(string, args[i], strlen(args[i]));
    }
    if (newline) {
        strcat(string, "\n");
    }

    /* write string */
    if (!testing) {
        if (outFilename) {
            int outFd = open(outFilename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
            if (outFd < 0) {
                showError("echo: cannot open %s for write: %d\n", outFilename, errno);
                free(string);
                return 1;
            }
            write(outFd, string, strlen(string));
            close(outFd);
        } else {
            showInfo(string);
        }
    }
    else {
        if (outFilename) {
            if (!newline) {
                strcat(string, "\n");
            }
            showInfo("(echo > %s) %s", outFilename, string);
        } else {
            showInfo("(echo) %s", string);
        }
    }

    free(string);
    return 0;
}

int umountCommand(char * cmd, char * end) {
    char * path;

    if (!(cmd = getArg("umount", cmd, end, &path))) {
        showError("umount: path expected\n");
        return 1;
    }

    if (cmd < end) {
        showError("umount: unexpected arguments\n");
        return 1;
    }

    if (umount(path)) {
        showError("umount: umount %s failed: %d\n", path, errno);
        return 1;
    }

    return 0;
}

int mkdirCommand(char * cmd, char * end) {
    char * dir;
    int ignoreExists = 0;

    cmd = getArg("mkdir", cmd, end, &dir);

    if (cmd && !strcmp(dir, "-p")) {
        ignoreExists = 1;
        cmd = getArg("mkdir", cmd, end, &dir);
    }

    if (!cmd) {
        showError("mkdir: directory expected\n");
        return 1;
    }

    if (mkdir(dir, 0755)) {
        if (!ignoreExists && errno == EEXIST) {
            showError("mkdir: failed to create %s: %d\n", dir, errno);
            return 1;
        }
    }

    return 0;
}

int sleepCommand(char * cmd, char * end) {
    char *delaystr;
    int delay;

    if (!(cmd = getArg("sleep", cmd, end, &delaystr))) {
        showError("sleep: delay expected\n");
        return 1;
    }

    delay = atoi(delaystr);
    (void)sleep(delay);

    return 0;
}

int lvmLvActivateCommand(char * cmd, char * end) {
    char * dev_tag;
    char * vg_name;
    char * lv_name;
    char * dev_name;

    if (!(cmd = getArg("lvm-lv-activate", cmd, end, &dev_tag))) {
        showError("lvm-lv-activate: missing dev-tag\n");
        return 1;
    }
    if (parseDevTag(dev_tag, NULL) == NULL) {
        showError("lvm-lv-activate: invalid dev-tag\n");
        return 1;
    }

    if (!(cmd = getArg("lvm-lv-activate", cmd, end, &vg_name))) {
        showError("lvm-lv-activate: missing vgname\n");
        return 1;
    }

    if (!(cmd = getArg("lvm-lv-activate", cmd, end, &lv_name))) {
        showError("lvm-lv-activate: missing lvname\n");
        return 1;
    }

    if (cmd < end) {
        showError("lvm-lv-activate: unexpected arguments\n");
        return 1;
    }

    if (runBinary2("lvm-lv-activate", "/sbin/lvm", "vgchange", "-ay") != 0) {
        /* callee prints error message */
        return 1;
    }

    dev_name = waitForDev("lvm-lv-activate", dev_tag);
    if (dev_name != NULL) {
        free(dev_name);
    }

    return 0;
}

int bcacheActivateCacheDeviceCommand(char * cmd, char * end) {
    char * bcacheRegisterFile;
    char * cache_set_uuid;
    char * device;
    char * devName = NULL;
    char cache_set_dir_path[PATH_MAX];
    int rc = 0;

    if (!quiet) {
        bcacheRegisterFile = "/sys/fs/bcache/register";
    } else {
        bcacheRegisterFile = "/sys/fs/bcache/register_quiet";
    }

    if (!(cmd = getArg("bcache-cache-device-activate", cmd, end, &cache_set_uuid))) {
        showError("bcache-cache-device-activate: missing cache-set-uuid\n");
        return 1;
    }

    if (!(cmd = getArg("bcache-cache-device-activate", cmd, end, &device))) {
        showError("bcache-cache-device-activate: missing device\n");
        return 1;
    }

    if (cmd < end) {
        showError("bcache-cache-device-activate: unexpected arguments\n");
        return 1;
    }

    if (!testing) {
        devName = waitForDev("bcache-cache-device-activate", device);
        if (devName != NULL) {
            device = devName;
        }

        int fd = open(bcacheRegisterFile, O_WRONLY, 0);
        if (fd < 0) {
            showError("bcache-cache-device-activate: failed to open %s: %d\n", bcacheRegisterFile, errno);
            rc = 1;
            goto done;
        }

        if (write(fd, device, strlen(device)) != strlen(device)) {
            showError("bcache-cache-device-activate: failed to write %s: %d\n", bcacheRegisterFile, errno);
            close(fd);
            rc = 1;
            goto done;
        }

        close(fd);

        snprintf(cache_set_dir_path, sizeof(cache_set_dir_path), "/sys/fs/bcache/%s", cache_set_uuid);
        while (true) {
            if (access(cache_set_dir_path, F_OK) == 0) {
                break;
            }
            (void)sleep(1);
        }
    }
    else {
        showInfo("(activate)\n");
    }

done:
    if (devName != NULL) {
        free(devName);
    }
    return rc;
}

int bcacheActivateBackingDeviceCommand(char * cmd, char * end) {
    char * bcacheRegisterFile;
    char * dev_tag;
    char * device;
    char * devName = NULL;
    int rc = 0;

    if (!quiet) {
        bcacheRegisterFile = "/sys/fs/bcache/register";
    } else {
        bcacheRegisterFile = "/sys/fs/bcache/register_quiet";
    }

    if (!(cmd = getArg("bcache-backing-device-activate", cmd, end, &dev_tag))) {
        showError("bcache-backing-device-activate: missing dev-tag\n");
        return 1;
    }

    if (!(cmd = getArg("bcache-backing-device-activate", cmd, end, &device))) {
        showError("bcache-backing-device-activate: missing device\n");
        return 1;
    }

    if (cmd < end) {
        showError("bcache-backing-device-activate: unexpected arguments\n");
        return 1;
    }

    if (parseDevTag(dev_tag, NULL) == NULL) {
        showError("bcache-backing-device-activate: invalid dev-tag\n");
        return 1;
    }

    if (!testing) {
        devName = waitForDev("bcache-backing-device-activate", device);
        if (devName != NULL) {
            device = devName;
        }

        int fd = open(bcacheRegisterFile, O_WRONLY, 0);
        if (fd < 0) {
            showError("bcache-backing-device-activate: failed to open %s: %d\n", bcacheRegisterFile, errno);
            rc = 1;
            goto done;
        }

        if (write(fd, device, strlen(device)) != strlen(device)) {
            showError("bcache-backing-device-activate: failed to write %s: %d\n", bcacheRegisterFile, errno);
            close(fd);
            rc = 1;
            goto done;
        }

        close(fd);
    }
    else {
        showInfo("(activate)\n");
    }

    /* wait backing device to appear in bcache-activate-finish command, not here */
    bcache_count++;

done:
    if (devName != NULL) {
        free(devName);
    }
    return rc;
}

int bcacheActivateFinishCommand(char * cmd, char * end) {
    if (bcache_count == 0) {
        return 0;
    }

    if (!testing) {
        for (;;) {
            int my_bcache_count = 0;
            DIR *dir;

            if (!(dir = opendir("/dev"))) {
                showError("bcache-activate-finish: failed to open /dev\n");
                return 1;
            }

            while (true) {
                struct dirent *d;

                errno = 0;
                if (!(d = readdir(dir))) {
                    if (errno) {
                        showError("bcache-activate-finish: failed to read directory /dev\n");
                        closedir(dir);
                        return 1;
                    }
                    break;    /* end of directory */
                }

                if (strlen(d->d_name) > strlen("bcache") && strncmp(d->d_name, "bcache", strlen("bcache")) == 0) {
                    if (d->d_name[strlen("bcache")] >= '0' && d->d_name[strlen("bcache")] <= '9') {
                        my_bcache_count++;
                    }
                }
            }

            closedir(dir);

            if (my_bcache_count >= bcache_count) {
                break;
            }

            (void)sleep(1);
        }
    }
    else {
        showInfo("(finish)\n");
    }

    return 0;
}

#define COMMAND_COMPARE(cmd, start, next) \
    (sizeof((cmd)) - 1 == (next) - (start) && strncmp((cmd), (start), (next) - (start)) == 0)

int runStartup() {
    int fd;
    char contents[32768];
    int i;
    char * start, * end;
    char * chptr;
    int rc;

    fd = open(STARTUPRC, O_RDONLY, 0);
    if (fd < 0) {
        showError("Cannot open %s: %d\n", STARTUPRC, errno);
        return 1;
    }

    i = read(fd, contents, sizeof(contents) - 1);
    if (i == (sizeof(contents) - 1)) {
        showError("Failed to read %s -- file too large.\n", STARTUPRC);
        close(fd);
        return 1;
    }

    contents[i] = '\0';

    start = contents;
    while (*start) {
        while (isspace(*start) && *start && (*start != '\n')) start++;

        if (*start == '#')
            while (*start && (*start != '\n')) start++;

        if (*start == '\n') {
            start++;
            continue;
        }

        if (!*start) {
            showWarn("(last line in %s is empty)\n", STARTUPRC);
            continue;
        }

        /* start points to the beginning of the command */
        end = start + 1;
        while (*end && (*end != '\n')) end++;
        if (!*end) {
            showWarn("(last line in %s missing \\n -- skipping)\n", STARTUPRC);
            start = end;
            continue;
        }

        /* end points to the \n at the end of the command */
        chptr = start;
        while (chptr < end && !isspace(*chptr)) chptr++;

        /* print command */
        if (!quiet) {
            showInfo("%.*s\n", (int)(end - start), start);
        }

        /* execute command */
        if (COMMAND_COMPARE("echo", start, chptr)) {
            rc = echoCommand(chptr, end);
        } else if (COMMAND_COMPARE("sleep", start, chptr)) {
            rc = sleepCommand(chptr, end);
        } else if (COMMAND_COMPARE("mkdir", start, chptr)) {
            rc = mkdirCommand(chptr, end);
        } else if (COMMAND_COMPARE("insmod", start, chptr)) {
            rc = insmodCommand(chptr, end);
        } else if (COMMAND_COMPARE("mount", start, chptr)) {
            rc = mountCommand(chptr, end);
        } else if (COMMAND_COMPARE("mount-file", start, chptr)) {
            rc = mountFileCommand(chptr, end);
        } else if (COMMAND_COMPARE("mount-overlayfs", start, chptr)) {
            rc = mountOverlayfsCommand(chptr, end);
        } else if (COMMAND_COMPARE("mount-btrfs", start, chptr)) {
            rc = mountBtrfsCommand(chptr, end);
        } else if (COMMAND_COMPARE("mount-bcachefs", start, chptr)) {
            rc = mountBcachefsCommand(chptr, end);
        } else if (COMMAND_COMPARE("umount", start, chptr)) {
            rc = umountCommand(chptr, end);
        } else if (COMMAND_COMPARE("switchroot", start, chptr)) {
            rc = switchrootCommand(chptr, end);
        } else if (COMMAND_COMPARE("bcache-cache-device-activate", start, chptr)) {
            rc = bcacheActivateCacheDeviceCommand(chptr, end);
        } else if (COMMAND_COMPARE("bcache-backing-device-activate", start, chptr)) {
            rc = bcacheActivateBackingDeviceCommand(chptr, end);
        } else if (COMMAND_COMPARE("bcache-activate-finish", start, chptr)) {
            rc = bcacheActivateFinishCommand(chptr, end);
        } else {
            *chptr = '\0';
            rc = otherCommand(start, chptr + 1, end, 1);
        }

        /* give user a chance to inspect errors, it won't affect the normal procedure since no error should occur */
        if (rc) {
            (void)sleep(10);
        }

        start = end + 1;
    }

    close(fd);
    return rc;
}

int main(int argc, char **argv) {
    bool force = false;
    int rc;

    testing = (getppid() != 0) && (getppid() != 1);

    if (!testing) {
        kmsg_fd = open("/dev/kmsg", O_WRONLY | O_CLOEXEC);   /* O_CLOEXEC: forked process (including switchroot) won't get kmsg_fd */
        if (kmsg_fd < 0) {
            /* unable to show anything */
            return 1;
        }
    }

    if (testing) {
        argv++, argc--;
        while (argc && **argv == '-') {
            if (!strcmp(*argv, "--force")) {
                force = true;
                testing = false;
                argv++, argc--;
            } else if (!strcmp(*argv, "--quiet")) {
                quiet = true;
                argv++, argc--;
            } else {
                showError("main: unknown argument %s\n", *argv);
                return 1;
            }
        }
    }

    if (!testing) {
        if (mount("none", "/sys", "sysfs", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL)) {
            showError("main: error %d mounting %s as %s\n", errno, "/sys", "sysfs");
            return 1;
        }
        if (mount("none", "/proc", "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL)) {
            showError("main: error %d mounting %s as %s\n", errno, "/proc", "proc");
            return 1;
        }
        if (mount("none", "/run", "tmpfs", MS_NOSUID|MS_NODEV, "mode=755,nr_inodes=0")) {
            /* don't limit size and inode count, we should use other means to restrict large files and file count */
            showError("main: error %d mounting %s as %s\n", errno, "/run", "tmpfs");
            return 1;
        }
        if (mount("none", "/tmp", "tmpfs", MS_NOSUID|MS_NODEV, "mode=1777,size=100%,nr_inodes=1m")) {
            /* FIXME: /tmp should be change to use both mem and fs (it should not be mounted here), so that there would be no size limit */
            showError("main: error %d mounting %s as %s\n", errno, "/tmp", "tmpfs");
            return 1;
        }
        if (mount("none", "/dev", "devtmpfs", MS_NOSUID, "mode=755,size=4m")) {
            showError("main: error %d mounting %s as %s\n", errno, "/dev", "devtmpfs");
            return 1;
        }
    }

    if (!testing) {
        if (hasKernelArg("main", "quiet")) {
            quiet = true;
        }
    }

    if (!quiet) {
        showInfo("program version %s\n", VERSION);
    }

    if (force && !quiet) {
        showWarn("(forcing normal run)\n");
    }

    if (testing && !quiet) {
        showWarn("(running in test mode)\n");
    }

    if (blkid_get_cache(&mycache, NULL) < 0) {
        showError("main: error get blkid cache\n");
        return 1;
    }

    rc = runStartup();

    return rc;
}
